﻿using System;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Net;
using System.Runtime.InteropServices;
using System.Security;
using System.Threading;
using System.Transactions;
using Microsoft.SqlServer.Server;
using SqlServerCodeflowDispatcher;

// ReSharper disable PartialTypeWithSinglePart
public partial class StoredProcedures
// ReSharper restore PartialTypeWithSinglePart
{
    [SqlProcedure]
    public static void DispatchCodeflow(Guid codeflowInstanceId, out int result)
    {
        result = 0;
        using (var connection = new SqlConnection("context connection=true"))
        {
            connection.Open();
            Uri workerEndpointUri;

            if (!GetWorkerEndpoint(codeflowInstanceId.ToString(), connection, out workerEndpointUri))
                return;

            result = DispatchCore(workerEndpointUri, connection) ? 1 : 0;
        }
    }

    static bool GetWorkerEndpoint(string codeflowInstanceId, SqlConnection connection, out Uri workerEndpointUri)
    {
        workerEndpointUri = null;

        var command = new SqlCommand
                          {
                              Connection = connection,
                              CommandText = "SELECT [Value] FROM CodeflowsConfiguration WHERE [Key] = 'WorkerEndpoint'"
                          };

        string workerEndpoint = null;

        try
        {
            workerEndpoint = command.ExecuteScalar() as string;
        }
        catch (Exception ex)
        {
            if (IsFatal(ex))
                throw;
            LogError(string.Format("There was an error while trying to read worker endpoint configuration: {0}", ex),
                     connection);
        }

        if (workerEndpoint == null)
        {
            LogError("Could not find the WorkerEndpoint configuration entry.", connection);
            return false;
        }

        workerEndpoint += workerEndpoint.EndsWith("\\") || workerEndpoint.EndsWith("/")
                              ? codeflowInstanceId
                              : "/" + codeflowInstanceId;

        if (!Uri.TryCreate(workerEndpoint, UriKind.Absolute, out workerEndpointUri))
        {
            LogError("Configured worker endpoint is not a valid Uri");
            return false;
        }

        return true;
    }

    static bool DispatchCore(Uri workerEndpoint, SqlConnection connection)
    {
        try
        {
            var client = new WebClient();
            client.DownloadData(workerEndpoint);
            return true;
        }
        catch (Exception ex)
        {
            if (IsFatal(ex))
                throw;

            var message = string.Format("There was an error while trying to dispatch the codeflow instance: {0}", ex);
            LogError(message, connection);
        }

        return false;
    }

    static void LogError(string message)
    {
        using (var connection = new SqlConnection())
        {
            connection.Open();
            LogError(message, connection);
        }
    }

    static void LogError(string message, SqlConnection connection)
    {
        Log(message, LogType.Error, connection);
    }

    static void Log(string message, LogType logType, SqlConnection connection)
    {
        try
        {
            var command = new SqlCommand
                              {
                                  Connection = connection,
                                  CommandText = "INSERT INTO CodeflowsLog ([Data], [Type]) VALUES (@Data, @LogType)",
                              };

            command.Parameters.AddWithValue("@Data", message);
            command.Parameters.AddWithValue("@LogType", logType);
            command.ExecuteNonQuery();
        }
        catch (Exception exception)
        {
            if (IsFatal(exception))
                throw;

            Trace.Write(string.Format("There was an error while tring to log the message: {0}. Exception: {1}",
                                      message,
                                      exception));
        }
    }


    static bool IsFatal(Exception exception)
    {
        return (exception is ThreadAbortException ||
                exception is SEHException ||
                exception is SecurityException ||
                (exception is OutOfMemoryException && !(exception is InsufficientMemoryException)));
    }
};